﻿using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using OpenCvSharp;
using System.Collections.Concurrent;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Runtime.InteropServices;
using VIA.ImageProcessing;
using VIA.Integration;
using VIA.Integration.Domain;
using VIA.Integration.Domain.Production;
using VIA.SiMoJi.Domain.Option;
using static VT.Framework.Utils.UtilTaskbarProgress;

namespace VIA.Project.Domain.DataStore
{
    //public interface IImageStorageService
    //{
    //}

    public class ImageStorageExService : IImageStorageService
    {
        private readonly int _timeout = 500;
        private ILogger<ImageStorageExService> _logger;
        private IFilePathBuilder _filePathBuilder;
        private BlockingCollection<Task> _tasks;
        private BlockingCollection<ImagePackage> _imagePackages;
        private SemaphoreSlim _pool;
        public event EventHandler<ImageStorageEventArgs> SourceImageStored;
        public event EventHandler<ImageStorageEventArgs> ResultImageStored;
        public event EventHandler<ImageStorageEventArgs> Completed;
        public event EventHandler<ImageStorageEventArgs> Added;
        public event EventHandler<ImageStorageErrorEventArgs> OnError;
        public DataStorageExOption _storageOption;

        public int Count { get => _imagePackages.Count; }

        public ImageStorageExService(IServiceProvider service, IOptionRepository optionRepository, ILogger<ImageStorageExService> logger)
        {
            _logger = logger;
            _filePathBuilder = service.GetRequiredService<IFilePathBuilder>();
            _storageOption = optionRepository.Get<DataStorageExOption>();
            _tasks = new BlockingCollection<Task>(10);
            _imagePackages = new BlockingCollection<ImagePackage>(20);
            _pool = new SemaphoreSlim(5, 5);
            Run();
        }

        void Run()
        {
            Task.Factory.StartNew(async () =>
            {
                foreach (var package in _imagePackages.GetConsumingEnumerable())
                {
                    _pool.Wait();
                    _ = Task.Factory.StartNew(() => { SavePackage(package); }).ContinueWith(x => { _pool.Release(); });
                }
            }, TaskCreationOptions.LongRunning);
        }

        private void SavePackage(ImagePackage package)
        {
            try
            {
                if (package.Image is ImageData image)
                {
                    if (!string.IsNullOrEmpty(package.SourceImageFilePath))
                    {
                        Directory.CreateDirectory(Path.GetDirectoryName(package.SourceImageFilePath));
                        image.Save(package.SourceImageFilePath);
                    }
                    if (!string.IsNullOrEmpty(package.ResultImageFilePath))
                    {
                        var bmp = Drawing(image, package.AlgorithmResult);
                        Directory.CreateDirectory(Path.GetDirectoryName(package.ResultImageFilePath));
                        SaveBitmap(bmp, package.ResultImageFilePath, null, package.Quality);
                    }
                }
                if (package.Image is ImageData3DU image3DU)
                {
                    if (!string.IsNullOrEmpty(package.SourceImageFilePath))
                    {
                        Directory.CreateDirectory(Path.GetDirectoryName(package.SourceImageFilePath));
                        image3DU.Save(package.SourceImageFilePath);
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, ex.Message);
            }
        }

        private Bitmap Drawing(ImageData image, IAlgorithmResult result)
        {
            Bitmap bitmap = ToBitmap(image);
            Graphics g = Graphics.FromImage(bitmap);
            if (result?.Strokes != null)
            {
                foreach (var item in result.Strokes)
                {
                    item.Draw(g);
                }
            }
            if (result?.Messages != null)
            {
                int height = image.Height;
                int count = result.Messages.Count();
                var msgs = result.Messages.ToList();
                int size1 = Math.Min((int)(height * 0.1), 60);
                int siez2 = Math.Min((int)(height * 0.6 / count), 40);
                int siez3 = Math.Min((int)(siez2 * 0.9), 30);
                g.DrawString($"{result.Quality}", new Font("微软雅黑", size1), result.Quality == Quality.Ok ? Brushes.Green : Brushes.Red, 30, 30);
                for (int i = 0; i < count; i++)
                {
                    g.DrawString($"{msgs[i]}", new Font("微软雅黑", siez3), result.Quality == Quality.Ok ? Brushes.Green : Brushes.Red, 30, 30 + (size1 + siez2 - siez3 + i * siez2) * 2);
                }
            }
            g.Dispose();
            return bitmap;
        }

        public Bitmap ToBitmap(ImageData imageData)
        {
            IntPtr intPtr = IntPtr.Zero;
            int width = imageData.Width;
            int height = imageData.Height;
            int stride = imageData.Width * 3;
            int a = stride % 4;
            if (a != 0)
            {
                stride = stride + 4 - a;
            }
            if (imageData.Channel == 3)
            {
                if (a != 0)
                {
                    byte[] data = new byte[stride * height];
                    IntPtr ptr = data.ToIntPtr();
                    for (int i = 0; i < height - 1; i++)
                    {
                        Marshal.Copy(imageData.Data, i * width * 3, ptr, width * 3);
                        ptr += stride;
                    }
                    intPtr = data.ToIntPtr();
                    return new Bitmap(width, height, stride, System.Drawing.Imaging.PixelFormat.Format24bppRgb, intPtr);
                }
                else
                {
                    intPtr = imageData.Data.ToIntPtr();
                    return new Bitmap(width, height, stride, System.Drawing.Imaging.PixelFormat.Format24bppRgb, intPtr);
                }
            }
            else if (imageData.Channel == 1)
            {
                byte[] data = new byte[stride * height];
                int dest = 0;
                int source = 0;
                for (int i = 0; i < height - 1; i++)
                {
                    for (int j = 0; j < width - 1; j++)
                    {
                        data[dest] = imageData.Data[source];
                        data[dest + 1] = imageData.Data[source];
                        data[dest + 2] = imageData.Data[source];
                        dest += 3;
                        source++;
                    }
                    dest += stride - width * 3;
                }
                intPtr = data.ToIntPtr();
                return new Bitmap(width, height, stride, System.Drawing.Imaging.PixelFormat.Format24bppRgb, intPtr);
            }
            else
            {
                throw new NotImplementedException("原图像的通道数必须为1或3！");
            }
        }

        public void SaveBitmap(Bitmap bmp, string filePath, Integration.Domain.Production.ImageFormat? format, int? qty)
        {
            try
            {
                if (format == null)
                {
                    bmp.Save(filePath);
                }
                else
                {
                    var q = qty ??= 75;
                    switch (format)
                    {
                        case Integration.Domain.Production.ImageFormat.BMP:
                            bmp.Save(Path.ChangeExtension(filePath, ".bmp"), System.Drawing.Imaging.ImageFormat.Bmp);
                            break;
                        case Integration.Domain.Production.ImageFormat.PNG:
                            bmp.Save(Path.ChangeExtension(filePath, ".png"), System.Drawing.Imaging.ImageFormat.Png);
                            break;
                        case Integration.Domain.Production.ImageFormat.JPG:
                            SaveAsJPEG(bmp, Path.ChangeExtension(filePath, ".jpg"), q);
                            break;
                        case Integration.Domain.Production.ImageFormat.TIF:

                            bmp.Save(Path.ChangeExtension(filePath, ".tif"), System.Drawing.Imaging.ImageFormat.Tiff);
                            break;
                        default:
                            bmp.Save(Path.ChangeExtension(filePath, ".bmp"), System.Drawing.Imaging.ImageFormat.Bmp);
                            break;
                    }
                }
            }
            catch (Exception)
            {
                throw;
            }
        }

        public void SaveAsJPEG(Bitmap bmp, string FileName, int Qty)
        {
            try
            {
                EncoderParameter p = new EncoderParameter(Encoder.Quality, Qty);
                EncoderParameters ps = new EncoderParameters(1);
                ps.Param[0] = p;
                bmp.Save(FileName, GetCodecInfo("image/jpeg"), ps);
            }
            catch
            {
                throw;
            }
        }

        private string GetImageFormatFileExtension(Integration.Domain.Production.ImageFormat format)
        {
            return format switch
            {
                Integration.Domain.Production.ImageFormat.BMP => ".bmp",
                Integration.Domain.Production.ImageFormat.PNG => ".png",
                Integration.Domain.Production.ImageFormat.JPG => ".jpg",
                Integration.Domain.Production.ImageFormat.TIF => ".tif",
                _ => ".bmp",
            };
        }

        private ImageCodecInfo GetCodecInfo(string mimeType)
        {
            ImageCodecInfo[] CodecInfo = ImageCodecInfo.GetImageEncoders();
            foreach (ImageCodecInfo ici in CodecInfo)
            {
                if (ici.MimeType == mimeType) return ici;
            }
            return null;
        }

        public ImageStorageItem Add(ImageStoragePackage package)
        {
            IAlgorithmResult result = package.Result;
            result ??= package.Source?.Result;
            if (result == null) return null;
            bool isSave = false;
            var pck = new ImagePackage();
            if (result is ImageAlgorithmResult result1)
            {
                AddImagePackage(result1.Image, result1, package);
            }
            else if (result is ProfileImageAlgorithmResult result2)
            {
                AddImagePackage(result2.Image.Luminance, result2, package);
                AddImagePackage(result2.Image.Depth, result2, package);
            }
            var item = new ImageStorageItem(package, _storageOption);
            Added.Invoke(this, new ImageStorageEventArgs(item));
            return item;
        }

        private void AddImagePackage(ImageData image, IAlgorithmResult result, ImageStoragePackage package)
        {
            if (image == null) return;
            bool isSave = false;
            var pck = new ImagePackage();
            if (result.Quality == Quality.Ok && (_storageOption.OKSourceImageStorageOption.IsSaveImage || _storageOption.OKResultImageStorageOption.IsSaveImage))
            {
                isSave = true;
                pck.Image = image;
                pck.AlgorithmResult = result;
                if (_storageOption.OKSourceImageStorageOption.IsSaveImage)
                {
                    var sourceImageFilePath = _filePathBuilder.BuildSourceImagePath(_storageOption.SourceImagePath, package);
                    pck.SourceImageFilePath = Path.ChangeExtension(sourceImageFilePath, GetImageFormatFileExtension(_storageOption.OKSourceImageStorageOption.Format));
                }
                if (_storageOption.OKResultImageStorageOption.IsSaveImage)
                {
                    var resultImageFilePath = _filePathBuilder.BuildResultImagePath(_storageOption.ResultImagePath, package);
                    pck.ResultImageFilePath = Path.ChangeExtension(resultImageFilePath, GetImageFormatFileExtension(_storageOption.OKResultImageStorageOption.Format));
                    pck.Quality = _storageOption.OKResultImageStorageOption.CompressionRatio;
                }
            }
            else if (result.Quality != Quality.Ok && (_storageOption.NGSourceImageStorageOption.IsSaveImage || _storageOption.NGResultImageStorageOption.IsSaveImage))
            {
                isSave = true;
                pck.Image = image;
                pck.AlgorithmResult = result;
                if (_storageOption.NGSourceImageStorageOption.IsSaveImage)
                {
                    var sourceImageFilePath = _filePathBuilder.BuildSourceImagePath(_storageOption.SourceImagePath, package);
                    pck.SourceImageFilePath = Path.ChangeExtension(sourceImageFilePath, GetImageFormatFileExtension(_storageOption.NGSourceImageStorageOption.Format));
                }
                if (_storageOption.NGResultImageStorageOption.IsSaveImage)
                {
                    var resultImageFilePath = _filePathBuilder.BuildResultImagePath(_storageOption.ResultImagePath, package);
                    pck.ResultImageFilePath = Path.ChangeExtension(resultImageFilePath, GetImageFormatFileExtension(_storageOption.NGResultImageStorageOption.Format));
                }
            }
            if (isSave) _imagePackages.TryAdd(pck);
        }

        private void AddImagePackage(ImageData3DU image, IAlgorithmResult result, ImageStoragePackage package)
        {
            if (image == null) return;
            if ((result.Quality == Quality.Ok && _storageOption.OKSourceImageStorageOption.IsSaveImage) || (result.Quality != Quality.Ok && _storageOption.NGSourceImageStorageOption.IsSaveImage))
            {
                var pck = new ImagePackage(image, result);
                var sourceImageFilePath = _filePathBuilder.BuildSourceImagePath(_storageOption.SourceImagePath, package);
                pck.SourceImageFilePath = Path.ChangeExtension(sourceImageFilePath, ".tif");
                _imagePackages.Add(pck);
            }
        }

        internal class ImagePackage
        {
            private int quality = 75;
            public IImageData Image { get; set; }
            public IAlgorithmResult AlgorithmResult { get; set; }
            public string SourceImageFilePath { get; set; }
            public string ResultImageFilePath { get; set; }
            public int Quality { get => quality; set { if (value >= 1 && value <= 100) quality = value; } }
            public ImagePackage() { }
            public ImagePackage(IImageData image) { Image = image; }
            public ImagePackage(IImageData image, IAlgorithmResult result) { Image = image; AlgorithmResult = result; }
        }
    }
}
